﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.IO;

using WebPALTT.Lib;

namespace WebPALTT.UI
{
    public partial class ScenarioBuilder : Form
    {
        private Scenario mScenario;
        private FileInfo mFileInfo;
        private bool mInMem;
        private bool mUnsaved;

        #region Constructors

        public ScenarioBuilder()
        {
            InitializeComponent();
            mScenario = new Scenario("", "");
            mFileInfo = null;
            mInMem = false;
            mUnsaved = false;
        }

        public ScenarioBuilder(Scenario scenario) : this()
        {
            mScenario = scenario;
            RefreshView();
            mFileInfo = null;
            mInMem = true;
            mUnsaved = false;
        }

        #endregion

        private bool CheckSave()
        {
            if (mInMem || !mUnsaved)
                return true;

            DialogResult result = MessageBox.Show(this, "Do you want to save the changes to this scenario?", 
                "Scenario Builder", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);
            if (result == DialogResult.Yes)
                return SaveScenario();
            else if (result == DialogResult.No)
                return true;
            else
                return false;
        }
        
        private bool SaveScenario(bool toNewFile = false)
        {
            if (mScenario == null)
                return false;
            if (toNewFile || mFileInfo == null)
            {
                SaveFileDialog sfdialog = new SaveFileDialog();
                sfdialog.Title = "Save Scenario";
                sfdialog.Filter = "Binary File|*.bin|All Files|*.*";
                sfdialog.AddExtension = true;
                sfdialog.DefaultExt = "bin";
                if (sfdialog.ShowDialog(this) == DialogResult.OK)
                    mFileInfo = new FileInfo(sfdialog.FileName);
                else
                    return false;
            }
            try
            {
                FileSerialization.SerializeObject<Scenario>(mScenario, mFileInfo.FullName);
                mUnsaved = false;                
            }
            catch (Exception ex)
            { 
                MessageBox.Show(this, ex.Message, "Error Occured During File Save", MessageBoxButtons.OK, MessageBoxIcon.Error); 
                return false; 
            }
            RefreshView();
            return true;
        }        

        private void ScenarioBuilder_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (!CheckSave())
                e.Cancel = true;
        }

        #region Menu Strip

        private void tsmi_File_New_Click(object sender, EventArgs e)
        {
            if (CheckSave())
            {
                mScenario = new Scenario("", "");
                mFileInfo = null;
                mInMem = false;
                mUnsaved = true;
                ResetUI();
                RefreshView();
            }
        }

        private void tsmi_File_Open_Click(object sender, EventArgs e)
        {
            if (mScenario == null)
                return;
            if (!CheckSave())
                return;

            OpenFileDialog ofdialog = new OpenFileDialog();
            ofdialog.Title = "Open Scenario";
            ofdialog.Filter = "Binary File|*.bin|All files|*.*";
            ofdialog.Multiselect = false;
            if (ofdialog.ShowDialog(this) == DialogResult.OK)
            {
                try
                {
                    mScenario = FileSerialization.DeSerializeObject<Scenario>(ofdialog.FileName);
                    propertyGrid_Selection.SelectedObject = null;
                    mInMem = false;
                    mUnsaved = false;
                    mFileInfo = new FileInfo(ofdialog.FileName);
                }
                catch (Exception ex)
                { MessageBox.Show(this, ex.Message, "Error Occured During File Load", MessageBoxButtons.OK, MessageBoxIcon.Error); }
                
                mScenario.Actions.RefreshIndexes(true, null);
                ResetUI(); 
                RefreshView();                
            }
        }

        private void tsmi_File_Save_Click(object sender, EventArgs e)
        {
            SaveScenario();
        }

        private void tsmi_File_SaveAs_Click(object sender, EventArgs e)
        {
            SaveScenario(true);
        }

        private void tsmi_File_Exit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void tsmi_NewNode_WebRequest_New_Click(object sender, EventArgs e)
        {
            Add_New_Action(new ActionItem_WebRequest(""));
        }

        private void tsmi_NewNode_WebRequest_Import_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofdialog = new OpenFileDialog();
            ofdialog.Title = "Open File";
            ofdialog.Filter = "Session Archive|*.saz|Zip Archive|*.zip|All files|*.*";
            ofdialog.Multiselect = false;
            if (ofdialog.ShowDialog(this) == DialogResult.OK)
            {
                try
                {
                    foreach (ActionItem action in FileImport.Import(ofdialog.FileName))
                        Add_New_Action(action);
                }
                catch (Exception ex) { MessageBox.Show(this, ex.Message, "Error Occured During Import", MessageBoxButtons.OK, MessageBoxIcon.Error); }
            }
        }

        private void tsmi_NewNode_Script_Click(object sender, EventArgs e)
        {
            ActionItem_Script action = new ActionItem_Script("",
                new string[] { },
                new string[] { "System", "System.Collections.Generic", "System.Linq", "System.Text", "System.Net", "WebPALTT.Lib" },
                "ScriptAction_" + (mScenario.Actions.Count + 1), "return \"\";");
            Add_New_Action(action);
        }

        private void tsmi_NewNode_Sleep_Click(object sender, EventArgs e)
        {
            Add_New_Action(new ActionItem_Sleep(""));
        }

        private void tsmi_NewNode_Conditional_If_Click(object sender, EventArgs e)
        {
            Add_New_Action(new ActionItem_If(""));
        }

        private void tsmi_NewNode_Conditional_Loop_Click(object sender, EventArgs e)
        {
            Add_New_Action(new ActionItem_Loop(""));
        }

        private void tsmi_NewNode_Variable_New_Click(object sender, EventArgs e)
        {
            Add_New_Action(new ActionItem_Variable(""));
        }

        private void tsmi_DeleteNode_Click(object sender, EventArgs e)
        {
            if (treeView_Scenario.SelectedNode == null)
                return;
            try
            {
                TreeNode SelectedNode = treeView_Scenario.SelectedNode;
                Guid key;
                if (!Guid.TryParse(SelectedNode.Name, out key))
                {
                    if (!Guid.TryParse(SelectedNode.Parent.Name, out key))
                        throw new InvalidOperationException("Parent node invalid.");

                    if (MessageBox.Show(this, "Are you sure you want to delete all Actions in this folder?",
                            "Confirm Delete", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == System.Windows.Forms.DialogResult.No)
                        return;
                    ActionItem item = mScenario.Actions.GetChild(key);
                    if (item.ActionType == ActionType.If && SelectedNode.Text.ToUpper().Equals("THEN"))
                        ((ActionItem_If)item).ThenActions.Clear();
                    else if (item.ActionType == ActionType.If && SelectedNode.Text.ToUpper().Equals("ELSE"))
                        ((ActionItem_If)item).ElseActions.Clear();
                    else if (item.ActionType == ActionType.Loop)
                        ((ActionItem_Loop)item).Actions.Clear();
                }
                else
                {
                    if (MessageBox.Show(this, "Are you sure you want to delete this Action?", 
                        "Confirm Delete", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == System.Windows.Forms.DialogResult.No)
                        return;

                    ActionItem item = mScenario.Actions.GetChild(key);
                    if (item.Parent == null)
                        mScenario.Actions.RemoveAt(item.Index);
                    else if (item.Parent.ActionType == ActionType.If && SelectedNode.Parent.Text.ToUpper().Equals("THEN"))
                        ((ActionItem_If)item.Parent).ThenActions.RemoveAt(item.Index);
                    else if (item.Parent.ActionType == ActionType.If && SelectedNode.Parent.Text.ToUpper().Equals("ELSE"))
                        ((ActionItem_If)item.Parent).ElseActions.RemoveAt(item.Index);
                    else if (item.Parent.ActionType == ActionType.Loop)
                        ((ActionItem_Loop)item.Parent).Actions.RemoveAt(item.Index);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(this, "An error has occured during the delete operation.\nError:\t" + ex.Message, 
                    "An Error Has Occured", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }            
            if (!mInMem && !mUnsaved)
                    mUnsaved = true;

            mScenario.Actions.RefreshIndexes(true, null);
            ResetUI();
            RefreshView();
        }        
        
        private void tsb_Refresh_Click(object sender, EventArgs e)
        {
            ResetUI();
            RefreshView();
        }

        private void tsmi_Build_Validate_Click(object sender, EventArgs e)
        {
            AppDomain appDomain = AppDomain.CreateDomain("ValidationDomain");
            ScriptValidator runner = (ScriptValidator)appDomain.CreateInstanceFromAndUnwrap(
                Assembly.GetAssembly(typeof(ScriptValidator)).Location,
                typeof(ScriptValidator).FullName);

            string[] errors = runner.Validate(mScenario);             

            AppDomain.Unload(appDomain);
            
            if (errors.Length == 0)
                MessageBox.Show(this, "No errors found.", "Code Compiler", MessageBoxButtons.OK, MessageBoxIcon.Information);
            else
            {
                string validationMessage = "";
                foreach (string s in errors)
                    validationMessage += s + "\r\n";
                MessageBox.Show(this, validationMessage, "Code Compiler", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        private void tsmi_Debug_Start_Click(object sender, EventArgs e)
        {
            try
            {
                DebugForm debug = new DebugForm(mScenario);
                debug.ShowDialog(this);
            }
            catch (Exception ex)
            {
                MessageBox.Show(this, "An error has occured:\r\n\t" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }        
        
        private void tsmi_Help_API_Click(object sender, EventArgs e)
        {
            Help.ShowHelp(this, "Resources/API - WebPALTT.chm");
        }
        
        private void tsb_NewNode_Click(object sender, EventArgs e)
        {
            tsb_NewNode.ShowDropDown();
        }

        #endregion

        #region TreeView

        private void ResetUI()
        {
            propertyGrid_Selection.SelectedObject = null;
            textEditorControl_Selection_AddText("");
            textEditorControl_Selection.Visible = false;
        }
        
        private void RefreshView()
        {
            if (mScenario == null)
                mScenario = new Scenario("", "");
            treeView_Scenario.Nodes.Clear();
            treeView_Scenario.Nodes.AddRange(GetTreeNodes(mScenario.Actions.ToArray()));
            treeView_Scenario.ExpandAll();

            if (mInMem)
                this.Text = "Scenario Builder - In Memory";
            else if(mFileInfo == null)
                this.Text = "Scenario Builder - ";
            else
                this.Text = "Scenario Builder - " + mFileInfo.FullName + (mUnsaved ? "*" : "");
        }

        private TreeNode[] GetTreeNodes(ActionItem[] actions)
        {
            List<TreeNode> list = new List<TreeNode> { };
            foreach (ActionItem item in actions)
            {
                TreeNode newnode = null;
                if (item.GetType().Equals(typeof(ActionItem_WebRequest)))
                    newnode = new TreeNode(item.Name, 0, 0);
                else if (item.GetType().Equals(typeof(ActionItem_Script)))
                    newnode = new TreeNode(item.Name, 1, 1);
                else if (item.GetType().Equals(typeof(ActionItem_Sleep)))
                    newnode = new TreeNode(item.Name, 2, 2);
                else if (item.GetType().Equals(typeof(ActionItem_If)))
                {
                    newnode = new TreeNode(item.Name, 3, 3);
                    newnode.Nodes.Add(new TreeNode("Then", 6, 6));
                    newnode.Nodes.Add(new TreeNode("Else", 6, 6));
                    newnode.Nodes[0].Nodes.AddRange(GetTreeNodes(((ActionItem_If)item).ThenActions.ToArray()));
                    newnode.Nodes[1].Nodes.AddRange(GetTreeNodes(((ActionItem_If)item).ElseActions.ToArray()));
                }
                else if (item.GetType().Equals(typeof(ActionItem_Loop)))
                {
                    newnode = new TreeNode(item.Name, 4, 4);
                    newnode.Nodes.Add(new TreeNode("Actions", 6, 6));
                    newnode.Nodes[0].Nodes.AddRange(GetTreeNodes(((ActionItem_Loop)item).Actions.ToArray()));
                }
                else if (item.GetType().Equals(typeof(ActionItem_Variable)))
                    newnode = new TreeNode(item.Name, 5, 5);
                else
                    continue;
                newnode.Name = item.Key.ToString();
                list.Add(newnode);
            }
            return list.ToArray();
        }

        private void treeView_Scenario_BeforeSelect(object sender, TreeViewCancelEventArgs e)
        {
            e.Node.SelectedImageIndex = e.Node.ImageIndex;
        }

        private void treeView_Scenario_AfterSelect(object sender, TreeViewEventArgs e)
        {
            List<Guid> guids = new List<Guid> { };

            TreeNode node = e.Node;
            while (node.Parent != null)
            {
                Guid newGuid;
                if(Guid.TryParse(node.Name, out newGuid))
                    guids.Add(newGuid);
                node = node.Parent;
            }
            Guid newGuid2;
            if (Guid.TryParse(node.Name, out newGuid2))
                guids.Add(newGuid2);

            ActionItem action = mScenario.Actions[guids.Last()];

            for (int i = guids.Count - 2; i >= 0; i--)
            {
                if (action.ActionType == ActionType.Loop)
                    action = ((ActionItem_Loop)action).Actions[guids[i]];
                else if (action.ActionType == ActionType.If)
                {
                    ActionItem_If ifAction = (ActionItem_If)action;
                    if (ifAction.ThenActions.Contains(guids[i]))
                        action = ifAction.ThenActions[guids[i]];
                    else if (ifAction.ElseActions.Contains(guids[i]))
                        action = ifAction.ElseActions[guids[i]];
                    else
                        throw new Exception("A extremely unexpected error has occured");
                }
            }
            if (action == null)
            {
                tsb_Refresh_Click(null, null);
                return;
            }
            propertyGrid_Selection.SelectedObject = action;
            if (action.GetType().Equals(typeof(ActionItem_Script)))
            {
                textEditorControl_Selection_AddText(((ActionItem_Script)action).Code);
                textEditorControl_Selection.Visible = true;
                textEditorControl_Selection.Enabled = true;
            }
            else if (action.GetType().Equals(typeof(ActionItem_If)))
            {
                string code = "if(" + ((ActionItem_If)action).Condition + ")\n{\n\tThen;\n}\nelse\n{\n\tElse;\n}";
                textEditorControl_Selection_AddText(code);
                textEditorControl_Selection.Visible = true;
                textEditorControl_Selection.Enabled = false;
            }
            else if (action.GetType().Equals(typeof(ActionItem_Variable)))
            {
                string code = "private " + (((ActionItem_Variable)action).IsStatic ? "static " : "") +
                    ((ActionItem_Variable)action).Type + " " + action.Name + (String.IsNullOrEmpty(((ActionItem_Variable)action).Value) ? "" : " = " +
                    ((ActionItem_Variable)action).Value) + ";";
                textEditorControl_Selection_AddText(code);
                textEditorControl_Selection.Visible = true;
                textEditorControl_Selection.Enabled = false;
            }
            else
                textEditorControl_Selection.Visible = false;
        }

        private void treeView_Scenario_ItemDrag(object sender, ItemDragEventArgs e)
        {
            DoDragDrop(e.Item, DragDropEffects.Move);
        }

        private void treeView_Scenario_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Move;
        }

        private void treeView_Scenario_DragDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent("System.Windows.Forms.TreeNode", false))
            {
                ActionItemCollection backup = ObjectCopier.Clone<ActionItemCollection> (mScenario.Actions);
                try
                {
                    // Get Info about the tree node that was moved and the one that it was dropped on.
                    TreeNode destinationNode = ((TreeView)sender).GetNodeAt(((TreeView)sender).PointToClient(new Point(e.X, e.Y)));
                    TreeNode newNode = (TreeNode)e.Data.GetData("System.Windows.Forms.TreeNode");

                    Guid newNodeKey;
                    Guid destNodeKey;
                    int newNodeIndex;
                    int destNodeIndex;
                    ActionItem newNodeAction;
                    ActionItem destNodeAction;

                    // Ignore moving of internal list folders (the Then / Else / Actions folders for If and Loop Actions) 
                    // and ignore cases where the node was dropped on it self or in empty space.
                    if (!Guid.TryParse(newNode.Name, out newNodeKey))
                        return;
                    if (destinationNode == null || newNode.Equals(destinationNode))
                        return;

                    // Get the ActionItem, index and key for the node that was moved.
                    newNodeAction = mScenario.Actions.GetChild(newNodeKey);
                    newNodeIndex = newNodeAction.Index;

                    // Get the ActionItem, index and key for the destination node.
                    if (!Guid.TryParse(destinationNode.Name, out destNodeKey))
                    {
                        if (!Guid.TryParse(destinationNode.Parent.Name, out destNodeKey))
                            throw new InvalidOperationException("Parent node invalid.");

                        destNodeAction = mScenario.Actions.GetChild(destNodeKey);
                        destNodeIndex = -1;
                    }
                    else
                    {
                        destNodeAction = mScenario.Actions.GetChild(destNodeKey);
                        destNodeIndex = destNodeAction.Index;
                    }

                    // Remove node being moved
                    if (newNodeAction.Parent == null)
                        mScenario.Actions.RemoveAt(newNodeIndex);
                    else if (newNodeAction.Parent.ActionType == ActionType.If && newNode.Parent.Text.ToUpper().Equals("THEN"))
                        ((ActionItem_If)newNodeAction.Parent).ThenActions.RemoveAt(newNodeIndex);
                    else if (newNodeAction.Parent.ActionType == ActionType.If && newNode.Parent.Text.ToUpper().Equals("ELSE"))
                        ((ActionItem_If)newNodeAction.Parent).ElseActions.RemoveAt(newNodeIndex);
                    else if (newNodeAction.Parent.ActionType == ActionType.Loop)
                        ((ActionItem_Loop)newNodeAction.Parent).Actions.RemoveAt(newNodeIndex);

                    // Add node to destination
                    if (destNodeIndex == -1)
                    {
                        if (destNodeAction.ActionType == ActionType.If && destinationNode.Text.ToUpper().Equals("THEN"))
                            ((ActionItem_If)destNodeAction).ThenActions.Add(newNodeAction);
                        else if (destNodeAction.ActionType == ActionType.If && destinationNode.Text.ToUpper().Equals("ELSE"))
                            ((ActionItem_If)destNodeAction).ElseActions.Add(newNodeAction);
                        else if (destNodeAction.ActionType == ActionType.Loop)
                            ((ActionItem_Loop)destNodeAction).Actions.Add(newNodeAction);
                    }
                    else if (destNodeAction.Parent == null)
                        mScenario.Actions.Insert(destNodeIndex, newNodeAction);
                    else
                    {
                        if (destNodeAction.Parent.ActionType == ActionType.If && destinationNode.Parent.Text.ToUpper().Equals("THEN"))
                            ((ActionItem_If)destNodeAction.Parent).ThenActions.Insert(destNodeIndex, newNodeAction);
                        else if (destNodeAction.Parent.ActionType == ActionType.If && destinationNode.Parent.Text.ToUpper().Equals("ELSE"))
                            ((ActionItem_If)destNodeAction.Parent).ElseActions.Insert(destNodeIndex, newNodeAction);
                        else if (destNodeAction.Parent.ActionType == ActionType.Loop)
                            ((ActionItem_Loop)destNodeAction.Parent).Actions.Insert(destNodeIndex, newNodeAction);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(this, "An error has occured during the drag and drop operation.\n" +
                        "Any changes made during the operation will be reverted.\n\nError:\t" + ex.Message, "An Error Has Occured", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    mScenario.Actions.Clear();
                    mScenario.Actions.AddRange(backup);
                }

                // Refresh
                if (!mInMem && !mUnsaved)
                    mUnsaved = true;

                mScenario.Actions.RefreshIndexes(true, null);
                RefreshView();
            }
        }

        #endregion

        #region Script Editor

        private void textEditorControl_Selection_CompletionRequest(object sender, DigitalRune.Windows.TextEditor.Completion.CompletionEventArgs e)
        {
            if (textEditorControl_Selection.CompletionWindowVisible)
                return;
            // e.Key == '\0' means that the user triggered the CompletionRequest by pressing <Ctrl> + <Space>.
            if (e.Key == '\0')
            {
                textEditorControl_Selection.ShowCompletionWindow(new CodeCompletionDataProvider(), e.Key, false);
            }
            else if (char.IsLetter(e.Key))
            {
                textEditorControl_Selection.ShowCompletionWindow(new CodeCompletionDataProvider(), e.Key, true);
            }
        }

        private void textEditorControl_Selection_AddText(string code)
        {            
            textEditorControl_Selection.SetHighlighting("C#");
            textEditorControl_Selection.Text = code;
            textEditorControl_Selection.Refresh();
        }

        private void textEditorControl_Selection_DocumentChanged(object sender, DigitalRune.Windows.TextEditor.Document.DocumentEventArgs e)
        {
            if (propertyGrid_Selection.SelectedObject != null && propertyGrid_Selection.SelectedObject.GetType().Equals(typeof(ActionItem_Script)))
                ((ActionItem_Script)propertyGrid_Selection.SelectedObject).Code = textEditorControl_Selection.Text;
        }
        #endregion

        private void Add_New_Action(ActionItem item)
        {
            if (String.IsNullOrWhiteSpace(item.Name))
            {
                string name = "";
                switch (item.ActionType)
                {
                    case ActionType.If:
                        name = "IfStatement";
                        break;
                    case ActionType.Loop:
                        name = "Loop";
                        break;
                    case ActionType.Script:
                        name = "Script";
                        break;
                    case ActionType.Sleep:
                        name = "Sleep";
                        break;
                    case ActionType.Variable:
                        name = "Variable";
                        break;
                    case ActionType.WebRequest:
                        name = "WebRequest";
                        break;
                }
                ActionItem[] list = mScenario.Actions.FindAll(
                    delegate(ActionItem action)
                    {
                        if (action.Name.StartsWith(name))
                            return true;
                        else
                            return false;
                    }, true);
                string[] nameList = new string[list.Length];
                for (int i = 0; i < list.Length; i++)
                    nameList[i] = list[i].Name;
                int j = 1;
                while (true)
                {
                    if (!nameList.Contains(name + "_" + j))
                    {
                        name = name + "_" + j;
                        break;
                    }
                    j++;
                }
                item.Name = name;
            }

            if (treeView_Scenario.SelectedNode != null)
            {                
                TreeNode selectedNode = treeView_Scenario.SelectedNode;
                Guid key;
                if (!Guid.TryParse(selectedNode.Name, out key))
                {
                    if (!Guid.TryParse(selectedNode.Parent.Name, out key))
                        throw new InvalidOperationException("Parent node invalid.");

                    ActionItem parent = mScenario.Actions.GetChild(key);
                    if (parent.ActionType == ActionType.If && selectedNode.Text.ToUpper().Equals("THEN"))
                        ((ActionItem_If)parent).ThenActions.Add(item);
                    else if (parent.ActionType == ActionType.If && selectedNode.Text.ToUpper().Equals("ELSE"))
                        ((ActionItem_If)parent).ElseActions.Add(item);
                    else if (parent.ActionType == ActionType.Loop)
                        ((ActionItem_Loop)parent).Actions.Add(item);
                }
                else
                    mScenario.Actions.Add(item);
            }
            else
                mScenario.Actions.Add(item);

            mScenario.Actions.RefreshIndexes(true, null);

            if (!mInMem && !mUnsaved)
                mUnsaved = true;
            RefreshView();
        }
    }
}
